/**	@file Number.cpp */
#include "Number.h"
#include <sstream>
#include <string>
#include <limits>
namespace sympycpp {
int Number::precision = 0;
int Number::getInt() const {
		if (isInt()) {
			mpz_class a = ratioVal_.get_num();
			int s = sign() == plus ? 1 : -1;
			return s * (int)a.get_ui();
		}
		throw NotInt();
}
void Number::setToFactorial(const int x) {
	assert(x >= 0);	
	mpz_t a;
	mpz_init(a);
	mpz_fac_ui(a, x); // factorial from gmp
	ratioVal_ = mpz_class(a);
	isRatio_ = true;
	sign_ = plus;	
}
void Number::setToPower(const int e) {
	if (e >= 0 ) {
		if (isRatio_) { // numerator and denominator neet to be powered individually
			mpq_class tm  = getN_Rational();
			mpz_t n;
			mpz_t d;
			mpz_init(n);
			mpz_init(d);
			mpz_pow_ui(n, tm.get_num_mpz_t(), e);
			mpz_pow_ui(d, tm.get_den_mpz_t(), e);
			mpq_set_num(tm.get_mpq_t(), n); 
			mpq_set_den(tm.get_mpq_t(), d); 
			tm.canonicalize();
			setValue(tm);
		}
		else {
			mpf_class tm = getN_Real();
			mpf_t rop;
			mpf_init(rop);
			mpf_pow_ui(rop, tm.get_mpf_t(), e);
			setValue(mpf_class(rop));
		}
	}
	else {
		if (isRatio_) { // numerator and denominator neet to be powered individually
			mpq_class tm  = getN_Rational();
			mpz_t n;
			mpz_t d;
			mpz_init(n);
			mpz_init(d);
			mpz_pow_ui(n, tm.get_num_mpz_t(), e*(-1));
			mpz_pow_ui(d, tm.get_den_mpz_t(), e*(-1));
			mpq_set_num(tm.get_mpq_t(), d); 
			mpq_set_den(tm.get_mpq_t(), n); 
			tm.canonicalize();
			setValue(tm);
		}
		else {
			mpf_class tm = getN_Real();
			mpf_t rop;
			mpf_t rop2;
			mpf_t rop3;
			mpf_init(rop);
			mpf_init(rop2);
			mpf_init(rop3);
			mpf_set_ui(rop3, 1);
			mpf_pow_ui(rop, tm.get_mpf_t(), e*(-1));
			mpf_div(rop2, rop3, rop);
			setValue(mpf_class(rop2));
		}	
	}
}
void Number::setToAddition(const Number & a, const Number & b) {
	if (a.isReal()) {
		if (b.isReal()) { // real + real --> real
			realVal_ = a.getN_Real() + b.getN_Real();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal(); // if real+real --> integer, transform the number
		}
		else { // real + rational -> real
			realVal_ = a.getN_Real() + b.getN_Rational();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();
		}
	}
	else {
		if (b.isReal()) { // rational + real --> real
			realVal_ = a.getN_Rational() + b.getN_Real();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();
		}
		else { // rational + rational --> rational
			ratioVal_ = a.getN_Rational() + b.getN_Rational();
			isRatio_ = true;
			if(ratioVal_ < 0) {
				sign_ = minus;
				ratioVal_ = ratioVal_*(-1);
			}
			else {
				sign_ = plus;
			}
		}

	}

}
void Number::setToSubtraction(const Number & a, const Number & b) {
	if (a.isReal()) {
		if (b.isReal()) { // real - real --> real
			realVal_ = a.getN_Real() - b.getN_Real();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
				else {
				sign_ = plus;
			}
			checkVal();
		}
		else { // real - rational --> real
			realVal_ = a.getN_Real() - b.getN_Rational();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();
		}
	}
	else {
		if (b.isReal()) { // real - real --> real
			realVal_ = a.getN_Rational() - b.getN_Real();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();
		}
		else { // rational - rational -->rational
			ratioVal_ = a.getN_Rational() - b.getN_Rational();
			isRatio_ = true;
			if(ratioVal_ < 0) {
				sign_ = minus;
				ratioVal_ = ratioVal_*(-1);
			}
			else {
				sign_ = plus;
			}
		}

	}

}
void Number::setToMultiplication(const Number & a, const Number & b) {
	if (a.isReal()) {
		if (b.isReal()) { // real * real --> real
			realVal_ = a.getN_Real() * b.getN_Real();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();
		}
		else { // real * rational --> real
			realVal_ = a.getN_Real() * b.getN_Rational();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();
		}
	}
	else {
		if (b.isReal()) { // rational * real --> real
			realVal_ = a.getN_Rational() * b.getN_Real();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();
		}
		else { // rational * rational --> rational
			ratioVal_ = a.getN_Rational() * b.getN_Rational();
			isRatio_ = true;
			if(ratioVal_ < 0) {
				sign_ = minus;
				ratioVal_ = ratioVal_*(-1);
			}
			else {
				sign_ = plus;
			}
		}

	}
}
void Number::setToDivision(const Number & a, const Number & b) {
	if (b.eq(0)) {
		throw ZeroDivision();
	}
	if (a.isReal()) {
		if (b.isReal()) { // real / real --> real
			realVal_ = a.getN_Real() / b.getN_Real();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();
		}
		else { // real / rational --> real
			realVal_ = a.getN_Real() / b.getN_Rational();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();

		}
	}
	else {
		if (b.isReal()) { // rational / real --> real
			realVal_ = a.getN_Rational() / b.getN_Real();
			isRatio_ = false;
			if(realVal_ < 0) {
				sign_ = minus;
				realVal_ = realVal_*(-1);
			}
			else {
				sign_ = plus;
			}
			checkVal();

		}
		else { // rational / rational --> rational
			ratioVal_ = a.getN_Rational() / b.getN_Rational();
			isRatio_ = true;
			if(ratioVal_ < 0) {
				sign_ = minus;
				ratioVal_ = ratioVal_*(-1);
			}
			else {
				sign_ = plus;
			}
		}

	}

}
N_Real Number::getN_Real() const {
	assert(!isRatio_);
	return sign() ? realVal_ : (-1)*realVal_;
}
N_Rational Number::getN_Rational() const {
	assert(isRatio_);
	return sign() ? ratioVal_ : (-1)*ratioVal_;
}
void Number::checkVal() {
	if (!isRatio_) {
		if (ceil(realVal_) == realVal_) {
			ratioVal_ = realVal_;
			isRatio_ = true;
		}
	}
}
Number::Number(const N_Real & realVal) : Ex(NUM), isRatio_(false) {
	if (realVal < 0) {
		sign_ = minus;
		realVal_ = realVal*(-1);
	}
	else {
		sign_ = plus;
		realVal_ = realVal;
	}
	checkVal();
}
Number::Number(const N_Rational & ratioVal) : Ex(NUM), isRatio_(true) {
	if (ratioVal < 0) {
		sign_ = minus;
		ratioVal_ = ratioVal * (-1);
	}
	else {
		sign_ = plus;
		ratioVal_ = ratioVal;
	}
}
Number::Number(const N_Real_init X) : Ex(NUM), isRatio_(false) {
	if(X < 0) {
		sign_ = minus;
		realVal_ = -X;
	}
	else{
		sign_ = plus;
		realVal_ = X;
	}
	checkVal();
}
Number::Number(const N_Rational_init X) : Ex(NUM), isRatio_(true) {
	if(X < 0) {
		sign_ = minus;
		ratioVal_ = -X;
	}
	else{
		sign_ = plus;
		ratioVal_ = X;
	}
	checkVal();
}
Number::Number(const int q, const char * X) : Ex(NUM), isRatio_(true) {
	ratioVal_ = X;
	ratioVal_.canonicalize();
	if(ratioVal_ < 0) {
		sign_ = minus;
		ratioVal_ = -ratioVal_;
	}
	else{
		sign_ = plus;
	}
}
Number::Number(const double q, const char * X) : Ex(NUM), realVal_(X), isRatio_(false)  {
	if(realVal_ < 0) {
		sign_ = minus;
		realVal_ = -realVal_;
	}
	else{
		sign_ = plus;
	}
	checkVal();
}
bool Number::isInt() const {
	if(isRatio_) {
		return mpz_class(ratioVal_.get_den_mpz_t()) == 1 && mpz_class(ratioVal_.get_num_mpz_t()) <= std::numeric_limits<int>::max();
	}
	else {
		return false;
	}
}
bool Number::isInteger() const {
	if(isRatio_) {
		return mpz_class(ratioVal_.get_den_mpz_t()) == 1;
	}
	else {
		return false;
	}
}

std::string Number::treeView(const int i ) const {
	std::ostringstream stream;
	spaces(i, stream);
	char ch = sign() ? '+' : '-';
	if (isRatio_) {
		stream << ">("<< ch <<")"<< ratioVal_ << std::endl;
	}
	else {
		stream << ">("<< ch <<")"<< realVal_ << std::endl;
	}
	return stream.str();
}
void Number::setValue(const Number & i) {
	isRatio_ = i.isRatio_;
	sign_ = i.sign_;
	if (isRatio_) {
		ratioVal_ = i.ratioVal_;
	}
	else {
		realVal_ = i.realVal_;
	}
}
void Number::setValue(const N_Rational & i) {
	isRatio_ = true;
	if(i < 0){
		sign_ = minus;
		ratioVal_ = i*(-1);
	}
	else{
		sign_ = plus;
		ratioVal_ = i;
	}
}
void Number::setValue(const N_Rational_init  i) {
	isRatio_ = true;
	if(i < 0){
		sign_ = minus;
		ratioVal_ = i*(-1);
	}
	else{
		sign_ = plus;
		ratioVal_ = i;
	}
}
void Number::setValue(const N_Real & i) {
	isRatio_ = false;
	if(i < 0){
		sign_ = minus;
		realVal_ = i*(-1);
	}
	else{
		sign_ = plus;
		realVal_ = i;
	}
}
void Number::setValue(const N_Real_init  i) {
	isRatio_ = false;
	if(i < 0){
		sign_ = minus;
		realVal_ = i*(-1);
	}
	else{
		sign_ = plus;
		realVal_ = i;
	}
}
void Number::sign(const Sign s) {
	if (!eq(0)) {
		sign_ = s;
	}
}
Number Number::abs() const {
	Number n(*this);
	n.sign(plus);
	return n;
}
std::string Number::str2() const {
	std::ostringstream s;
	if (sign()) {
		s << unsigned_str();
	}
	else {
		s << '-' << unsigned_str();
	}
	return s.str();
}
std::string Number::str() const {
	std::ostringstream s;
	s << str2();
	if(s.str().find( '/' ) == std::string::npos) {
		if (sign()) {
			return s.str();
		}
	}
	return "("+s.str()+")";	
}
std::string Number::unsigned_str() const {
	std::ostringstream s;
	if (isRatio_) {
		s << ratioVal_;
	}
	else {
		s.precision(precision);
		s << realVal_;
	}
	return s.str();
}
Ex * Number::diff(const Sym & x) const {
	return new Number(0);
}
bool Number::eq(const N_Real & X) const {
	const Number b(X);
	return this->eq(b); 
}
bool Number::eq(const N_Rational & X) const {
	const Number b(X);
	return this->eq(b);
}
bool Number::eq(const N_Real_init X) const {
	const Number b(X);
	return this->eq(b);
}
bool Number::eq(const N_Rational_init X) const {
	const Number b(X);
	return this->eq(b);
}
bool Number::eq(const Number & X) const {
	if (isRatio_) {
		return ratioVal_ == X.ratioVal_ && sign() == X.sign();
	}
	else {
		return realVal_ == X.realVal_ && sign() == X.sign();
	}
}
bool Number::lt(const N_Real & X) const {
	const Number b(X);
	return this->lt(b);
}
bool Number::lt(const N_Rational & X) const {
	const Number b(X);
	return this->lt(b);
}
bool Number::lt(const N_Real_init X) const {
	const Number b(X);
	return this->lt(b);
}
bool Number::lt(const N_Rational_init X) const {
	const Number b(X);
	return this->lt(b);
}
bool Number::lt(const Number & X) const {
	if (sign() != X.sign()) {
		return !sign();
	}
	if (isRatio_) {
		return ratioVal_ < X.ratioVal_;
	}
	else {
		return realVal_ < X.realVal_;
	}
}
}
